package org.gw.ylc.base.ctrl;

import net.sf.jasperreports.engine.*;
import org.gw.ylc.base.context.RequestContextHolder;
import org.gw.ylc.base.report.ReportConfig;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Controller;
import org.springframework.ui.jasperreports.JasperReportsUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.method.annotation.*;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodArgumentResolverComposite;
import org.springframework.web.method.support.InvocableHandlerMethod;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.*;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

@Controller
public class ExportCtrl implements InitializingBean {
	@Autowired
	private List<RequestMappingHandlerMapping> requestMappingHandlerMappings;
	@Autowired
	private List<RequestMappingHandlerAdapter> requestMappingHandlerAdapters;

	@Autowired(required = false)
	private List<ReportConfig> reportConfigList;

	private Map<String, ReportConfig> reportMap = new HashMap<String, ReportConfig>();
	private Map<String, HandlerMethod> handlerMap = new HashMap<String, HandlerMethod>();
	private HandlerMethodArgumentResolverComposite argumentResolvers;
	private WebBindingInitializer initializer;

	public HandlerMethod getHandlerMethod(String url) {
		for (RequestMappingHandlerMapping requestMappingHandlerMapping : requestMappingHandlerMappings) {
			Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
			for (Entry<RequestMappingInfo, HandlerMethod> item : handlerMethods.entrySet()) {
				RequestMappingInfo mapping = item.getKey();
				HandlerMethod method = item.getValue();
				for (String urlPattern : mapping.getPatternsCondition().getPatterns()) {
					if (urlPattern.equals(url)) {
						return method;
					}
				}
			}
		}
		return null;
	}

	@RequestMapping("/export/**")
	public void export(HttpServletRequest httpRequest, NativeWebRequest request, ModelAndViewContainer mavContainer,
			HttpServletResponse response) throws JRException, IOException {
		Object attribute = httpRequest.getAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE);
		String url = attribute.toString().replace("/export", "");
		if (!reportMap.containsKey(url)) {
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}
		HandlerMethod method;
		if (handlerMap.containsKey(url)) {
			method = handlerMap.get(url);
		} else {
			method = getHandlerMethod(url).createWithResolvedBean();
			handlerMap.put(url, method);
		}
		if (method == null) {
			response.sendError(HttpServletResponse.SC_NOT_FOUND);
			return;
		}
		ServletInvocableHandlerMethod m = new ServletInvocableHandlerMethod(method);
		m.setDataBinderFactory(
				new ServletRequestDataBinderFactory(new ArrayList<InvocableHandlerMethod>(), initializer));
		m.setHandlerMethodArgumentResolvers(argumentResolvers);
		try {
			RequestContextHolder.get().setPageRequest(new PageRequest(0, Integer.MAX_VALUE));
			Object invokeForRequest = m.invokeForRequest(request, mavContainer, new Object[] {});
			ReportConfig reportConfig = reportMap.get(url);
			Resource resource = new PathMatchingResourcePatternResolver().getResource(reportConfig.getResourceUrl());
			if (!resource.exists()) {
				response.sendError(HttpServletResponse.SC_NOT_FOUND);
				return;
			}
			Map<String, Object> map = new HashMap<String, Object>();
			JasperReport report = JasperCompileManager.compileReport(resource.getInputStream());
			Map<String, String[]> parameterMap = httpRequest.getParameterMap();
			map.putAll(parameterMap);
			map.put("page", invokeForRequest);
			JasperFillManager.fillReport(report, map);
			OutputStream os = response.getOutputStream();
			response.setHeader("Content-disposition",
					"attachment; filename=" + URLEncoder.encode(reportConfig.getFileName(), "utf-8"));
			JasperReportsUtils.renderAsXls(report, map, new JREmptyDataSource(), os);
			os.flush();
		} catch (Exception e) {
			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
			e.printStackTrace();
			return;
		}
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		this.argumentResolvers = new HandlerMethodArgumentResolverComposite();

		if (requestMappingHandlerAdapters.size() > 0) {
			for (RequestMappingHandlerAdapter adpter : requestMappingHandlerAdapters) {
				argumentResolvers.addResolvers(adpter.getArgumentResolvers());
				WebBindingInitializer webBindingInitializer = adpter.getWebBindingInitializer();
				if (webBindingInitializer != null) {
					this.initializer = webBindingInitializer;
				}
			}
		} else {
			argumentResolvers.addResolvers(getDefaultArgumentResolvers());
		}
		if (reportConfigList != null) {
			for (ReportConfig config : reportConfigList) {
				reportMap.put(config.getUrlMapping(), config);
			}
		}
	}

	private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<HandlerMethodArgumentResolver>();

		// Annotation-based argument resolution
		resolvers.add(new RequestParamMapMethodArgumentResolver());
		resolvers.add(new PathVariableMethodArgumentResolver());
		resolvers.add(new PathVariableMapMethodArgumentResolver());
		resolvers.add(new MatrixVariableMethodArgumentResolver());
		resolvers.add(new MatrixVariableMapMethodArgumentResolver());
		resolvers.add(new ServletModelAttributeMethodProcessor(false));
		resolvers.add(new RequestHeaderMapMethodArgumentResolver());

		// Type-based argument resolution
		resolvers.add(new ServletRequestMethodArgumentResolver());
		resolvers.add(new ServletResponseMethodArgumentResolver());
		resolvers.add(new RedirectAttributesMethodArgumentResolver());
		resolvers.add(new ModelMethodProcessor());
		resolvers.add(new MapMethodProcessor());
		resolvers.add(new ErrorsMethodArgumentResolver());
		resolvers.add(new SessionStatusMethodArgumentResolver());
		resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
		// Catch-all
		resolvers.add(new ServletModelAttributeMethodProcessor(true));

		return resolvers;
	}
}
